﻿<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>textbox2</title>
    <meta charset="utf-8" />
    <!--引入bootstrap测试插件兼容性-->
    <link href="Content/bootstrap.css" rel="stylesheet" />
    <link href="Content/bootstrap-theme.css" rel="stylesheet" />
    <style>
        .prompt-container {
            position: relative;
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
            margin: 0 !important;
            padding: 0 !important;
        }

            .prompt-container:before, .prompt-container:after {
                -webkit-box-sizing: border-box;
                -moz-box-sizing: border-box;
                box-sizing: border-box;
                margin: 0 !important;
                padding: 0 !important;
            }

        .prompt-wrapper {
            position: absolute;
            z-index: 999;
            /*border: 1px solid #ffd800;*/
            background: #FFF none repeat scroll 0% 0%;
            box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
            overflow-x: hidden;
            overflow-y: auto;
            line-height: 32px;
            font-size: 12px;
            height: auto;
            /*width: 200px;*/
            margin: 0 !important;
            padding: 0 !important;
        }

            .prompt-wrapper ul {
                /*width: 200px;*/
                margin: 0 !important;
                padding: 0 !important;
            }

            .prompt-wrapper li {
                list-style-type: none;
                cursor: pointer;
                height: 32px;
                /*width: 200px;*/
                margin: 0;
                padding: 0;
            }

                .prompt-wrapper li:hover {
                    background-color: #e2e1e1;/*鼠标划过时背景*/
                }

                .prompt-wrapper li.emphasis {
                    background-color:#0066FF;/*选中项背景*/
                    color:#FFF;
                }
    </style>
</head>
<body>
    <div class="container" style="background-color:#e2e1e1;">
        <form class="form-horizontal" role="form">
            <div class="form-group">
                <label for="firstname" class="col-sm-2 control-label">名字</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="firstname"
                           placeholder="请输入名字">
                </div>
            </div>
            <div class="form-group">
                <label for="lastname" class="col-sm-2 control-label">姓</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="lastname"
                           placeholder="请输入姓">
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <div class="checkbox">
                        <label>
                            <input type="checkbox"> 请记住我
                        </label>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="submit" class="btn btn-default">登录</button>
                </div>
            </div>
        </form>

        <form class="form-inline" role="form">
            <div class="form-group">
                <label for="firstname" class="col-sm-3 control-label">名字</label>
                <div class="col-sm-9">
                    <input type="text" class="form-control" id="firstname2" placeholder="请输入名字" autoComplete="off">
                </div>
            </div>
            <div class="form-group">
                <label for="lastname" class="col-sm-3 control-label">姓</label>
                <div class="col-sm-9">
                    <input type="text" class="form-control" id="lastname2" placeholder="请输入姓">
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-3 col-sm-9">
                    <div class="checkbox">
                        <label>
                            <input type="checkbox"> 请记住我
                        </label>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-3 col-sm-9">
                    <button type="submit" class="btn btn-default">登录</button>
                </div>
            </div>
        </form>
    </div>

    <script src="Scripts/jquery-1.11.3.js"></script>
    <script src="Scripts/bootstrap.js"></script>
    <script>
        $(document).ready(function () {
            $('#firstname').textbox2({ url: "test.ashx", minChar: 1, onSelect: select });
            $('#lastname').textbox2({ url: "test.ashx", minChar: 1, onSelect: select });
            $('#firstname2').textbox2({ url: "test.ashx", minChar: 1, onSelect: select });
            $('#lastname2').textbox2({ url: "test.ashx", minChar: 1, onSelect: select });
        });

        function select(o) {
            console.log(o);
        }

        (function ($) {
            var settings;
            var wrap = "<div class='prompt-container'><div class='prompt-wrapper'><ul><li></li></ul></div></div>";//定义弹出层容器
            var item_wrap = "<li data-index='{Index}'>{Html}</li>";   //定义选项容器标签
            var item_templet = "<span>{Microsoft}</apan>";            //定义选项内容
            /*
              item 配合 dataHandler函数 可以自定义侯选项显示格式和需要的数据格式。 
              注意：不要修改 wrap 和 item_wrap.     
            */
            
            $.fn.textbox2 = function (options) {
                _init(this);
                _hideLayer(this);

                settings = $.extend({ focus: null/*存储当前获得焦点的元素的jquery对象*/ }, $.fn.textbox2.defaults, options);

                return this.each(function (i, n) {
                    var $this = $(this);
                    var itemH = settings.autoStyle ? $this.innerHeight() : _getItemHeightByCss($this);
                    var thisSettings = {
                        "timeoutId": 0,
                        "timeoutId2": 0,
                        "itemIndex": 0,/*当前选择的下拉列表的索引，索引从1开始，0表示未选择*/
                        "formerKeywords": "",/*上一次从服务器请求成功关键词*/
                        "keywords": "",/*用户输入的值*/
                        "keywords2": "",/*用户选择的值*/
                        "textWidth": $this.innerWidth(),
                        "textHeight": $this.innerHeight(),
                        "textPL": $this.css("padding-left"),
                        "textPR": $this.css("padding-right"),
                        "width": settings.width == 0 ? $this.innerWidth() : settings.width,
                        "itemHeight": itemH,
                        "maxHeight": itemH * settings.length,
                        "height": 0,   //高度取决于具体数据长度
                        "serverResult": []  //服务器返回的数据
                    }

                    /*
                    oninput              Firefox2+, Chrome, Opera10+, Safari, IE 9+ (另一个版本:firefox4+,chrome,opera,fafari5+,IE9+)  Edge
                    onpropertychange    Internet Explorer6-8
                    oninput 和 onpropertychange 这两个事件在 IE9 中都有个小BUG，那就是通过右键菜单菜单中的剪切和删除命令删除内容的时候不会触发
                    */
                    var events = {
                        "input": text_valueChange,
                        "propertychange": text_valueChange,
                        "keydown": text_keydown,
                        "keyup": text_keyup,
                        "focus": text_focus,
                        "blur": text_blur,
                        "click": text_click
                    };
                    var data = { "thisSettings": thisSettings };

                    $this.on(events, data);
                });
            };

            //属性及其默认值
            $.fn.textbox2.defaults = {
                url: "#",
                width: 0/*设置提示框的宽度, 0表示自动（与当前作用的文本框同宽）*/,
                length: 5/*选择列表中可见选项的数目*/,
                autoStyle: true/*使选项高度与当前作用的文本框高度相同。为true 时将覆盖css中 item 高度、行高设置，左右padding 。 false时使用css样式*/,
                minChar: 1, //最小字符数(从第几个开始搜索)
                millisec: 100, //在执行搜索前需等待的毫秒数
                type: "post", //ajax 请求类型
                async: true,  //是否异步请求
                onSelect: null //选择项后触发的回调函数
            };

            function text_valueChange(e) {
                var thisData = e.data.thisSettings;
                var $this = $(this);

                thisData.keywords = $this.val().trim();
                getData(thisData, dataHandler);
            }
            function text_keyup(e) {
                var thisData = e.data.thisSettings;
                var keyCode = _get_keyCode(e);
                var $this = $(this); // settings.focus 和 $(this)是指向的同一个元素

                /*输入是字母，或数字 者退格键则需要重新请求*/
                if ((keyCode >= 65 && keyCode <= 90)/*字母*/
                    || keyCode >= 48 && keyCode <= 57/*0123456789*/
                    || keyCode >= 96 && keyCode <= 105/*0123456789*/
                    || keyCode == 32/*space*/
                    || keyCode == 8/*BackSpace*/) {

                    thisData.keywords = $this.val().trim();
                    getData(thisData, dataHandler);
                }

                /*
                在一次完整的按键中（按下并松开）keydown和keyup其实是各自独立的，它们的触发源虽然大多时候是同一元素，但有时候也可以不是同一元素。
                就比如在本示例中，按一次Tab键（按下并松开）keydown的触发源是失去焦点的元素，keyup的触发源则是获得焦点的元素。
                */
                //else if (keyCode == 9/*Tab*/) {
                //    /*经测试，这里的this指的是获取焦点的元素，要想取得失去焦点元素，请在keydown事件中处理*/
                //    //console.log("e松开了Tab  %o ", e);
                //    //console.log("this松开了Tab  %o ", this);
                //    //console.log("$(this)松开了Tab  %o ", $(this));
                //}
            }
            function text_keydown(e) {
                var thisData = e.data.thisSettings;
                var keyCode = _get_keyCode(e);
                /*37﹐38﹐39﹐40对应按下的方向鍵分別是 左﹐上﹐右﹐下*/
                switch (keyCode) {
                    case 38:
                        //event.preventDefault();
                        //break;
                    case 40:
                        event.preventDefault();

                        if (thisData.serverResult.length > 0) {
                            _showLayer($(this));
                            upItemIndex(thisData, keyCode);
                        }
                        break;
                    case 13: //回车
                        event.preventDefault();

                        if (thisData.serverResult.length > 0) {
                            _hideLayer($(this));

                            if (settings.onSelect != null) {
                                settings.onSelect(thisData.serverResult[thisData.itemIndex - 1]);
                            }
                        }
                        break;

                    //case 9:/*Tab*/
                    //    //console.log("e按下了Tab  %o ", e);
                    //    //console.log("this按下了Tab  %o ", this);
                    //    //console.log("$(this)按下了Tab  %o ", $(this));

                    //    _hideLayer($(this));
                }
            }
            function text_focus(e) {
                var thisData = e.data.thisSettings;
                settings.focus = $(this);
                thisData.itemIndex = 0;
                thisData.keywords2 = "";
            }
            function text_blur(e) {
                var $this = $(this);
                //console.log($this.attr("id") + "失去了焦点");

                var thisData = e.data.thisSettings;

                /*单击提示层时会先触发文本框的onblur、再触发当前onclick
                 *这样在文本框onblur事件处理函数中没法判断当前到底要不要隐藏提示层 
                 *所以加个延迟，如果失去焦点是因为点击提示框引起的，则取消执行 clearTimeout(thisData.timeoutId2);*/
                thisData.timeoutId2 = setTimeout(function () {
                    _hideLayer($this);
                }, 200);
            }
            function text_click(e) {
                //var thisSettings = e.data.thisSettings;
                //e.stopPropagation();//return false;
            }
            
            function item_click(e) {
                var thisData = e.data.thisSettings;

                clearTimeout(thisData.timeoutId2);//取消因当前文本框失去焦点时的隐藏

                var $text = $(this).parentsUntil('.prompt-container').last().parent().prev().focus();

                var index = parseInt($(this).attr("data-index"));//console.log("单击了项:"+index);

                setItemIndex(thisData, index);

                _hideLayer(settings.focus);

                if (settings.onSelect != null) {
                    settings.onSelect(thisData.serverResult[index - 1]);
                }
                event.stopPropagation(); //return false;
            }

            //获取服务器端数据
            function getData(thisData, process) {
                if (settings.minChar > 0 && thisData.keywords.length < settings.minChar) {
                    return;
                }
                if (thisData.keywords == thisData.formerKeywords) {
                    return;
                }

                clearTimeout(thisData.timeoutId);

                thisData.timeoutId = setTimeout(function () {

                    thisData.formerKeywords = thisData.keywords; //缓存本次请求的关键词
                    sendAjax(thisData, function (result, status) {
                        thisData.serverResult = result;
                        process(thisData);
                    });
                }, settings.millisec);
            }
            //发送ajax请求
            function sendAjax(thisData, process) {
                $.ajax({
                    type: settings.type,
                    async: settings.async,
                    url: settings.url,
                    data: { key: thisData.keywords },
                    dataType: "json",
                    cache: false,
                    success: process,
                    error: function (err) {
                        console.log("textbox2 ajax请求错误:" + err);
                    }
                });
            }
            //服务器数据处理
            function dataHandler(thisData) {
                var $this = settings.focus;
                _enptyLayer($this);

                if (thisData.serverResult.length > 0) {
                    var ul = $this.next(".prompt-container").find(".prompt-wrapper ul");
                    $.each(thisData.serverResult, function (i, n) {
                        /*结合 item_templet 和 下面这句实现自定义侯选项的显示格式*/
                        var itemHtml = item_templet.replace(/{Microsoft}/, n.Name);

                        var li = item_wrap.replace(/{Index}/, i + 1/*让选项索引从1开始，0有特殊作用*/).replace(/{Html}/, itemHtml);
                        ul.append(li);
                    });

                    var data = { thisSettings: thisData };
                    ul.find("li").on("click", data, item_click);

                    _setStyle(thisData);
                    _showLayer($this);
                }
            }

            //通过上下键来移动选择项索引
            function upItemIndex(thisData, keyCode) {
                var itemNum = thisData.serverResult.length;
                var itemNum2 = itemNum + 1; //增加一个值用来存储当前用户实际输入的值

                thisData.itemIndex = (keyCode == 38 ? itemNum2 - 1 : itemNum2 + 1) + thisData.itemIndex;
                thisData.itemIndex = thisData.itemIndex % itemNum2;

                itemIndex_change_Handler(thisData);
            }
            //直接设置选择项的索引
            function setItemIndex(thisData, newIndex) {
                thisData.itemIndex = newIndex;
                itemIndex_change_Handler(thisData);
            }
            //选择项索引变化后的处理函数（更新高亮，文本框值，设置滚动条位置）
            function itemIndex_change_Handler(thisData) {
                var $this = settings.focus;
                var li = $this.next(".prompt-container").find(".prompt-wrapper ul").children();
                li.removeClass("emphasis");

                if (thisData.itemIndex == 0) {
                    thisData.keywords2 = "";
                } else if (thisData.itemIndex > 0) {
                    var index = thisData.itemIndex-1;
                    li.eq(index).addClass("emphasis");

                    thisData.keywords = thisData.serverResult[index].Name;
                    thisData.keywords2 = thisData.keywords;
                }  

                $this.val(thisData.keywords);
                _scrollTop(thisData);
            }

            function _get_keyCode(event) {
                var myEvent = event || windows.event;
                var keyCode = (myEvent.keyCode) || (myEvent.which) || (myEvent.charCode);
                return keyCode;
            }

            //获取Css中定义的item高度
            function _getItemHeightByCss(o) {
                return o.next(".prompt-container").find(".prompt-wrapper li").outerHeight(true);
            }

            function _init($this) {
                if (!$this.is(":text")) {
                    alert("textbox只能作用在文本框上");
                }
                if ($this.attr("id").length < 1) {
                    alert("textbox作用的文本框必须指定Id");
                }

                //$this.attr("autoComplete","off");
                $this.val(""); //有些浏览器默认会记忆文本框

                $this.after(wrap);
            }
            function _enptyLayer() {
                if (arguments.length == 0) {
                    $(".prompt-wrapper ul").empty();
                } else {
                    arguments[0].next(".prompt-container").find(".prompt-wrapper ul").empty();
                }
            }
            function _setStyle(thisSettings) {
                var $this = settings.focus;
                var wrap = $this.next(".prompt-container");

                var itemNum = wrap.find(".prompt-wrapper ul").children().length;

                wrap.find(".prompt-wrapper").css({
                    "width": thisSettings.width,
                    "height": itemNum < settings.length ? thisSettings.itemHeight * itemNum : thisSettings.maxHeight,
                    "max-height": settings.maxHeight
                });
                if (settings.autoStyle) {
                    wrap.find(".prompt-wrapper li").css({
                        "height": thisSettings.itemHeight,
                        "line-height": thisSettings.itemHeight + "px",
                        "padding-left": thisSettings.textPL,
                        "padding_right": thisSettings.textPR
                    });
                }
            }
            function _showLayer($this) {
                $this.next(".prompt-container").show();
                //console.log("showLayer:" + $this.show("id"));
            }
            function _hideLayer($this) {
                $this.next(".prompt-container").hide();

                //console.log("hideLayer:" + $this.attr("id"));
            }

            //滚动条定位
            var _scrollTop = function (thisData) {
                var $this = settings.focus;
                var container = $this.next(".prompt-container").find(".prompt-wrapper"),
                    scrollTo = $this.next(".prompt-container").find(".prompt-wrapper ul").children();

                var temp = thisData.itemIndex - settings.length;
                if (temp > 0) {
                    container.scrollTop(
                        temp * thisData.itemHeight
                    );
                    //container.animate({ scrollTop: temp * thisData.itemHeight }, 100);
                } else {
                    container.scrollTop(0);
                    //container.animate({ scrollTop: 0 }, 100);
                }
            }
        })(jQuery);
    </script>
</body>
</html>
